/*
 *  Copyright 2010 Alexey Andreev.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.xthl.core.styling;

import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import javax.xml.XMLConstants;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.nop.util.*;
import org.nop.xml.*;
import org.xml.sax.SAXException;

/**
 *
 * @author Alexey Andreev
 */
public class StylerParser {
    private static final String XMLNS = "http://xthl.org/schemas/styling";
    private Schema schema;

    public StylerInfo parse(XmlElement element) {
        StylerInfo result = new StylerInfo();
        for (XmlElement viewSetElement : element.getChildElements(XMLNS, "viewSet")) {
            ViewStyleInfo viewInfo = new ViewStyleInfo();
            for (XmlElement viewElement : viewSetElement.getChildElements(XMLNS, "view")) {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < viewElement.getNodeCount(); ++i) {
                    sb.append(viewElement.getNode(i).getAsText().getValue());
                }
                try {
                    viewInfo.getViewTypes().add(Class.forName(sb.toString(), true,
                            Thread.currentThread().getContextClassLoader()));
                } catch (ClassNotFoundException ex) {
                    throw new StylingParseException("Class " + sb.toString() +
                            " not found");
                }
            }
            for (XmlElement depElement : viewSetElement.getChildElements(XMLNS, "depends")) {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < depElement.getNodeCount(); ++i) {
                    sb.append(depElement.getNode(i).getAsText().getValue());
                }
                try {
                    viewInfo.getDependantTypes().add(Class.forName(sb.toString(), true,
                            Thread.currentThread().getContextClassLoader()));
                } catch (ClassNotFoundException ex) {
                    throw new StylingParseException("Class " + sb.toString() +
                            " not found");
                }
            }
            for (XmlElement cssElement : viewSetElement.getChildElements(XMLNS, "css")) {
                viewInfo.getCssItems().add(parseCssItem(cssElement));
            }
            for (XmlElement scriptElement : viewSetElement.getChildElements(XMLNS, "script")) {
                viewInfo.getScripts().add(parseScript(scriptElement));
            }
            result.getViewStyles().add(viewInfo);
        }
        return result;
    }

    private ScriptInfo parseScript(XmlElement element) {
        ScriptInfo result = new ScriptInfo();
        XmlAttribute classAttr = element.getAttribute("", "class");
        Class<?> cls;
        try {
            cls = Class.forName(classAttr.getValue(), true,
                    Thread.currentThread().getContextClassLoader());
        } catch (ClassNotFoundException ex) {
            throw new StylingParseException("Class " + classAttr.getValue() +
                    " not found at " + classAttr.getLocation().toString());
        }
        List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
        for (XmlElement paramElement : element.getChildElements()) {
            XmlText text = paramElement.getNode(0).getAsText();
            if (paramElement.getName().equals("byte")) {
                parameterTypes.add(byte.class);
                result.getParameters().add(Byte.parseByte(text.getValue()));
            } else if (paramElement.getName().equals("short")) {
                parameterTypes.add(short.class);
                result.getParameters().add(Short.parseShort(text.getValue()));
            } else if (paramElement.getName().equals("int")) {
                parameterTypes.add(int.class);
                result.getParameters().add(Integer.parseInt(text.getValue()));
            } else if (paramElement.getName().equals("long")) {
                parameterTypes.add(long.class);
                result.getParameters().add(Long.parseLong(text.getValue()));
            } else if (paramElement.getName().equals("string")) {
                parameterTypes.add(String.class);
                result.getParameters().add(text.getValue());
            }
        }
        XmlAttribute methodAttr = element.getAttribute("", "method");
        Method method;
        try {
            method = cls.getMethod(methodAttr.getValue(), parameterTypes.toArray(
                    new Class<?>[0]));
        } catch (NoSuchMethodException ex) {
            throw new StylingParseException("Method " + methodAttr.getValue() + " not found " +
                    (methodAttr.getLocation() != null ? " at " + methodAttr.getLocation() : ""));
        }
        result.setMethod(method);
        return result;
    }

    private CssItemInfo parseCssItem(XmlElement element) {
        CssItemInfo result = new CssItemInfo();
        XmlAttribute classAttr = element.getAttribute("", "class");
        Class<?> cls;
        try {
            cls = Class.forName(classAttr.getValue(), true,
                    Thread.currentThread().getContextClassLoader());
        } catch (ClassNotFoundException ex) {
            throw new StylingParseException("Class " + classAttr.getValue() +
                    " not found at " + classAttr.getLocation().toString());
        }
        List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
        for (XmlElement paramElement : element.getChildElements()) {
            XmlText text = paramElement.getNode(0).getAsText();
            if (paramElement.getName().equals("byte")) {
                parameterTypes.add(byte.class);
                result.getParameters().add(Byte.parseByte(text.getValue()));
            } else if (paramElement.getName().equals("short")) {
                parameterTypes.add(short.class);
                result.getParameters().add(Short.parseShort(text.getValue()));
            } else if (paramElement.getName().equals("int")) {
                parameterTypes.add(int.class);
                result.getParameters().add(Integer.parseInt(text.getValue()));
            } else if (paramElement.getName().equals("long")) {
                parameterTypes.add(long.class);
                result.getParameters().add(Long.parseLong(text.getValue()));
            } else if (paramElement.getName().equals("string")) {
                parameterTypes.add(String.class);
                result.getParameters().add(text.getValue());
            }
        }
        XmlAttribute methodAttr = element.getAttribute("", "method");
        Method method;
        try {
            method = cls.getMethod(methodAttr.getValue(), parameterTypes.toArray(
                    new Class<?>[0]));
        } catch (NoSuchMethodException ex) {
            throw new StylingParseException("Method " + methodAttr.getValue() + " not found " +
                    (methodAttr.getLocation() != null ? " at " + methodAttr.getLocation() : ""));
        }
        result.setMethod(method);
        XmlAttribute mediaAttr = element.getAttribute("", "media");
        result.setMedia(mediaAttr != null ? mediaAttr.getValue() : "all");
        return result;
    }
    
    private void ensureSchema() {
        if (schema != null) {
            return;
        }
        try {
            SchemaFactory schemaFactory = SchemaFactory.newInstance(
                    XMLConstants.W3C_XML_SCHEMA_NS_URI);
            schema = schemaFactory.newSchema(StylerParser.class.getResource("styler.xsd"));
        } catch (SAXException ex) {
            throw new StylingException(ex);
        }
    }

    public StylerInfo parse(String fileName) {
        InputStream input = null;
        try {
            input = new FileInputStream(fileName);
            return parse(input);
        } catch (IOException ex) {
            throw new StylingException(ex);
        } finally {
            FinallyUtil.close(input);
        }
    }

    public StylerInfo parse(InputStream stream) {
        ensureSchema();
        XmlParser xmlParser = new XmlParser();
        xmlParser.setSchema(schema);
        SimpleTextLogger textLogger = new SimpleTextLogger();
        xmlParser.setTextLogger(textLogger);
        XmlElement element = xmlParser.parse(stream);

        StringBuilder sb = new StringBuilder();
        for (LogMessage msg : textLogger.getMessages()) {
            if (msg.getLevel() != LogLevel.ERROR) {
                continue;
            }
            sb.append("  at ").append(msg.getLocation()).append(" --> ").append(msg.getText())
                    .append("\n");
        }
        if (sb.length() > 0) {
            throw new StylingParseException("Some errors were found parsing XML file:\n" + sb);
        }
        return parse(element);
    }
}
